﻿using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using OfficeOpenXml;
using System.IO;
using System;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using Config.Internal;
using System.Text;

namespace Config.Edit
{

    public class ConfigEditor : Editor
    {

        const string ExcelPathFmt = "{0}/{1}";

        //2003版最大行数是65536行，最大列数是256列。Excel2007及以后的版本最大行数是1048576行，最大列数是16384列。
        //此处不考虑03版  插件只支持xlsx格式的excel
        const int MAX_COLUMN_COUNT = 16384;
        const int MAX_ROW_COUNT = 1048576;

        /// <summary>
        /// 作用域所在行数
        /// </summary>
        const int ROW_OF_PARAM_Assess = 4;
        /// <summary>
        /// 类型所在行数
        /// </summary>
        const int ROW_OF_PARAM_TYPE = 5;
        /// <summary>
        /// 变量名所在行数
        /// </summary>
        const int ROW_OF_PARAM_NAME = 6;

        [MenuItem("Config/Generate Config Script")]
        static void GenerateConfigScript()
        {
            PutLog("开始生成Excel脚本");
            var objs = Selection.objects;
            if (objs.Length == 0)
            {
                Debug.LogError("未选中任何Excel文件");
                return;
            }
            string path = AssetDatabase.GetAssetPath(objs[0]);
            if (path.EndsWith(".xlsx"))
            {
                ProcessExcel(path, GenerateSheetScript);
                PutLog("Excel脚本生成完成");
            }
            else
            {
                Debug.LogError("未选中任何Excel文件");
            }
        }

        [MenuItem("Config/Build All Excel")]
        static void BuildAllExcel()
        {
            PutLog("开始处理所有Excel表格");

            DirectoryInfo directoryInfo = new DirectoryInfo(ConfigConst.ExcelPath);
            FileInfo[] files = directoryInfo.GetFiles();
            for (int i = 0; i < files.Length; i++)
            {
                string tmp = files[i].Name;
                if (!files[i].Name.Contains(".meta"))
                {
                    //Debug.Log(tmp);
                    BuildExcel(string.Format(ExcelPathFmt, ConfigConst.ExcelPath, tmp));
                }
            }

            PutLog("所有Excel表格处理完成");

        }

        [MenuItem("Config/Build Selected Excel")]
        static void BuildSelectedExcel()
        {
            var objs = Selection.objects;
            if (objs.Length == 0)
            {
                Debug.LogError("未选中任何Excel文件");
                return;
            }
            string path = AssetDatabase.GetAssetPath(objs[0]);
            if (path.EndsWith(".xlsx"))
            {
                //Debug.LogError(path);
                BuildExcel(path);
            }
            else
            {
                Debug.LogError("未选中任何Excel文件");
            }
        }

        private static void GenerateSheetScript(ExcelWorksheet worksheet)
        {
            PutLog("处理页签: " + worksheet.Name);
            string className = worksheet.Cells["A2"].Value as string;
            string originalContent = "";
            string filePath = ConfigConst.ConfigScriptPath + className + ".cs";
            ExcelRange excelRange = worksheet.Cells;
            if (Directory.Exists(ConfigConst.ConfigScriptPath) && File.Exists(filePath))
            {
                originalContent = File.ReadAllText(ConfigConst.ConfigScriptPath + className + ".cs");
            }
            else
            {
                originalContent = File.ReadAllText(ConfigConst.SampleConfigScriptPath);
                string firstParamName = excelRange[ROW_OF_PARAM_NAME, 1].Value.ToString();
                originalContent = originalContent.Replace("CLASS_NAME", className);
                originalContent = originalContent.Replace("FIRST_PARAM_NAME", firstParamName);
            }
            if (string.IsNullOrEmpty(originalContent))
            {
                Debug.LogError("配置脚本模板文件不存在");
                return;
            }
            string startMarker = "// AUTO GEN CODE START";
            string endMarker = "// AUTO GEN CODE END";
            int startIndex = originalContent.IndexOf(startMarker);
            int endIndex = originalContent.IndexOf(endMarker);
            StringBuilder temp = new StringBuilder();

            // 获取参数信息
            int maxCol = 1;
            int maxRow = 1;
            GetSheetCR(excelRange, out maxCol, out maxRow);
            for (int i = 1; i <= maxCol; i++)
            {
                string paramAssess = excelRange[ROW_OF_PARAM_Assess, i].Value.ToString().ToLower();
                string paramType = excelRange[ROW_OF_PARAM_TYPE, i].Value.ToString().ToLower();
                string paramName = excelRange[ROW_OF_PARAM_NAME, i].Value.ToString();
                temp.Append(string.Format("        {0} {1} {2};\n", paramAssess, paramType, paramName));
            }
            string newContent = temp.ToString();
            temp.Length = 0;
            if (startIndex >= 0 && endIndex >= 0 && endIndex > startIndex)
            {
                // 如果两个标记都存在且顺序正确
                temp.Append(originalContent.Substring(0, startIndex + startMarker.Length));
                temp.Append("\n");
                temp.Append(newContent);
                temp.Append("        "); // 缩进
                temp.Append(originalContent.Substring(endIndex));
                if (!Directory.Exists(ConfigConst.ConfigScriptPath))
                    Directory.CreateDirectory(ConfigConst.ConfigScriptPath);
                File.WriteAllText(filePath, temp.ToString());
                AssetDatabase.Refresh();
            }
            else
            {
                Debug.LogError("配置脚本模板文件格式错误");
                return;
            }
        }

        private static void BuildExcel(string excelName)
        {
            PutLog("------------------------------开始处理 " + excelName + " ---------------------------------");
            ProcessExcel(excelName, BuildSheet);
            PutLog("------------------------------完成处理 " + excelName + " ---------------------------------");
        }

        /// <summary>
        /// 读取单个excel
        /// </summary>
        /// <param name="excelName"></param>
        private static void ProcessExcel(string excelPath, System.Action<ExcelWorksheet> processor)
        {
            using (ExcelPackage package = new ExcelPackage(new FileInfo(excelPath)))
            {
                ExcelWorksheets worksheets = package.Workbook.Worksheets;
                List<ExcelWorksheet> useSheets = new List<ExcelWorksheet>();
                object tmpValue;
                for (int i = 1; i <= worksheets.Count; i++)
                {
                    string log = "检查页签[" + worksheets[i].Name + "]状态: ";

                    tmpValue = worksheets[i].Cells["A1"].Value;
                    if (tmpValue == null || string.IsNullOrEmpty(tmpValue.ToString()))
                    {
                        PutLog(log + "单元格[A1]为空，未通过", LogType.Warning);
                        continue;
                    }
                    tmpValue = tmpValue.ToString().Trim().ToLower();
                    if (tmpValue.ToString() == "isconfig")
                    {
                        PutLog(log + "通过");
                        useSheets.Add(worksheets[i]);
                    }
                    else
                    {
                        PutLog(log + "单元格[A1]非\"isconfig\"，未通过", LogType.Warning);
                    }
                }
                foreach (ExcelWorksheet sheet in useSheets)
                {
                    processor?.Invoke(sheet);
                }
            }
        }
        /// <summary>
        /// 读取单张页签
        /// </summary>
        private static void BuildSheet(ExcelWorksheet worksheet)
        {
            PutLog("处理页签数据: " + worksheet.Name);

            string className = worksheet.Cells["A2"].Value as string;
            ExcelRange excelRange = worksheet.Cells;
            int maxCol = 1;
            int maxRow = 1;
            GetSheetCR(worksheet.Cells, out maxCol, out maxRow);
            string[] paramStr = new string[maxCol];
            List<string[]> dataList = new List<string[]>();
            for (int j = 1; j <= maxCol; j++)
            {
                paramStr[j - 1] = excelRange[ROW_OF_PARAM_NAME, j].Value.ToString();
            }
            for (int i = ROW_OF_PARAM_NAME + 1; i <= maxRow; i++)
            {
                string[] tmpstr = new string[maxCol];
                for (int j = 1; j <= maxCol; j++)
                {
                    if (excelRange[i, j].Value == null)
                    {
                        tmpstr[j - 1] = string.Empty;
                    }
                    else
                    {
                        tmpstr[j - 1] = excelRange[i, j].Value.ToString();
                    }
                }
                dataList.Add(tmpstr);
            }

            BuildConfig(className, className + "Config", paramStr, dataList);

            //Debug.Log("---------------");
            //for (int i = 0; i < paramStr.Length; i++)
            //{
            //    Debug.Log(paramStr[i]);
            //}
            //Debug.Log("---------------");

            //for (int i = 0; i < dataList.Count; i++)
            //{
            //    for (int j = 0; j < dataList[i].Length; j++)
            //    {
            //        Debug.Log(dataList[i][j]);
            //    }
            //}
        }
        /// <summary>
        /// 获取表页签的最大行数和列数
        /// 参数名所在行遍历获得列数
        /// </summary>
        private static void GetSheetCR(ExcelRange excelRange, out int maxCol, out int maxRow)
        {
            object tmpV;
            maxCol = 1;
            maxRow = 1;
            for (int i = 1; i <= MAX_COLUMN_COUNT; i++)
            {
                tmpV = excelRange[ROW_OF_PARAM_NAME, i].Value;
                if (tmpV == null || string.IsNullOrEmpty(tmpV.ToString()))
                {
                    maxCol = i - 1;
                    break;
                }
            }
            for (int i = 1; i <= MAX_ROW_COUNT; i++)
            {
                tmpV = excelRange[i, 1].Value;
                if (tmpV == null || string.IsNullOrEmpty(tmpV.ToString()))
                {
                    maxRow = i - 1;
                    break;
                }
            }
        }

        private static void BuildConfig(string dataClassName, string configClassName, string[] paramStr, List<string[]> dataList)
        {
            Assembly assembly = Assembly.GetAssembly(typeof(ConfigDataBase));
            string dataTypeName = string.Format(ConfigConst.Namespace, dataClassName);
            string configTypeName = string.Format(ConfigConst.Namespace, configClassName);
            Type dataType = assembly.GetType(dataTypeName);
            Type configType = assembly.GetType(configTypeName);

            string FileName = dataType.ToString();
            FileName = FileName.Substring(FileName.LastIndexOf('.') + 1);
            int indexRow = 0;
            try
            {
                string[] parameters = paramStr;   //表格上的变量名字
                var TObj = Activator.CreateInstance(configType);
                MethodInfo method = configType.GetMethod("AddData");
                for (; indexRow < dataList.Count; indexRow++)
                {
                    var FObj = Activator.CreateInstance(dataType);
                    string[] datas = dataList[indexRow];
                    for (int i = 0; i < parameters.Length; i++)
                    {
                        FieldInfo fieldInfo = dataType.GetField(parameters[i], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                        if (fieldInfo == null)
                        {
                            Debug.LogError("列[" + parameters[i] + "]在类中没有对应的成员");
                            return;
                        }
                        switch (fieldInfo.FieldType.Name.ToLower())
                        {
                            case "byte":
                                {
                                    fieldInfo.SetValue(FObj, byte.TryParse(datas[i], out byte val) ? val : 0);
                                    break;
                                }
                            case "int16":
                                {
                                    fieldInfo.SetValue(FObj, short.TryParse(datas[i], out short val) ? val : 0);
                                    break;
                                }
                            case "int":
                            case "int32":
                                {
                                    fieldInfo.SetValue(FObj, int.TryParse(datas[i], out int val) ? val : 0);
                                    break;
                                }
                            case "int64":
                                {
                                    fieldInfo.SetValue(FObj, long.TryParse(datas[i], out long val) ? val : 0);
                                    break;
                                }
                            case "uint16":
                                {
                                    fieldInfo.SetValue(FObj, ushort.TryParse(datas[i], out ushort val) ? val : 0);
                                    break;
                                }
                            case "uint32":
                                {
                                    fieldInfo.SetValue(FObj, uint.TryParse(datas[i], out uint val) ? val : 0);
                                    break;
                                }
                            case "uint64":
                                {
                                    fieldInfo.SetValue(FObj, ulong.TryParse(datas[i], out ulong val) ? val : 0);
                                    break;
                                }
                            case "single":
                                {
                                    fieldInfo.SetValue(FObj, float.TryParse(datas[i], out float val) ? val : 0);
                                    break;
                                }
                            case "double":
                                {
                                    fieldInfo.SetValue(FObj, double.TryParse(datas[i], out double val) ? val : 0);
                                    break;
                                }
                            case "bool":
                            case "boolean":
                                {
                                    fieldInfo.SetValue(FObj, bool.TryParse(datas[i], out bool val) ? val : false);
                                    break;
                                }
                            case "string":
                                {
                                    fieldInfo.SetValue(FObj, datas[i]);
                                    break;
                                }
                            default:
                                Debug.LogError(string.Format("type error {0}", fieldInfo.FieldType.Name));
                                return;
                        }
                    }
                    method.Invoke(TObj, new object[] { FObj });
                }
                IFormatter formatter = new BinaryFormatter();
                if (!Directory.Exists(ConfigConst.DestinationPath))
                    Directory.CreateDirectory(ConfigConst.DestinationPath);
                using (Stream stream = new FileStream(string.Format("{0}/{1}.bytes", ConfigConst.DestinationPath, FileName),
                    FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    formatter.Serialize(stream, TObj);
                }
                AssetDatabase.Refresh();
            }
            catch (Exception e)
            {
                string str = string.Empty;
                if (indexRow >= 0 && indexRow < dataList.Count)
                {
                    for (int i = 0; i < dataList[indexRow].Length; i++)
                    {
                        str += dataList[indexRow][i] + " ";
                    }
                }
                Debug.LogError(e.Message + " row[" + (indexRow + 1 + ROW_OF_PARAM_NAME) + "]=" + str);
            }
        }


        private static void PutLog(string log, LogType logType = LogType.Log)
        {
            switch (logType)
            {
                case LogType.Error:
                    break;
                case LogType.Warning:
                    Debug.LogWarning(log);
                    break;
                case LogType.Log:
                    Debug.Log(log);
                    break;
            }
        }
    }

}
